/*###################################################################
  > File Name: storage/storage/storage_status.c
  > Author: Vurtune
  > Mail: vurtune@foxmail.com
  > Created Time: Tue 26 Dec 2017 12:53:06 AM PST
###################################################################*/
#include "config.h"

#define DBG_SUBSYS S_LIBCLUSTER

#include "lich_api.h"

#include "etcd.h"
#include "lich_md.h"
#include "configure.h"
#include "storage_status.h"
#include "../nodepool/diskmap.h"

/**
 * @file 在admin节点上，更新存储池状态到etcd： /lich4/poolstatus/<poolName>
 *
 */

#define STORAGE_STATUS_PREFIX "poolstatus"

static char *__status__[] = {"Pending", "Available", "Readonly", "Degraded", "Unavailable"};

typedef enum {
        STATUS_PENDING = 0,
        STATUS_AVAILABLE,
        STATUS_READONLY,
        STATUS_DEGRADED,
        STATUS_UNAVAILABLE,
} status_t;

typedef struct {
        struct list_head hook;

        int rack;
        int node;
        status_t status;
        char pool[MAX_NAME_LEN];
} entry_t;

static void *__storage_status_recycle__(void *arg);
static int storage_status_set(char *pool, status_t status);


typedef struct {
        BOOL inited;
        sem_t sem;
} c_module_t;

static sem_t __sem__;
static BOOL inited = FALSE;


int storage_status_bh_init()
{
        int ret;

        ret = sem_init(&__sem__, 0, 0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sy_thread_create2(__storage_status_recycle__, NULL, "storage_status_update");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

void storage_status_update() {
        if (inited) {
                sem_post(&__sem__);
        }
}

#if 0
static int __iterate_callback__(void *poolmd, void *arg)
{
        int ret;
        entry_t *ent;
        struct list_head *pos;
        struct list_head *pool_list = arg;
        struct poolmap_entry_t *poolmap_ent = poolmd;

        ret = ymalloc((void **)&ent, sizeof(entry_t));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        strcpy(ent->pool, poolmap_ent->pool);

        diskmap_t *diskmap = &poolmap_ent->diskmap;

        ent->rack = diskmap->rack.count;
        ent->node = diskmap->node.count;

        storage_status_set(ent->pool, 0);
        return 0;
err_ret:
        return ret;
}
#endif


static int storagepool_get_repnum(const char *pool, int *usr_repnum)
{
        int ret;
        fileid_t id;
        fileinfo_t fileinfo;

        ret = md_root(&id, pool);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = md_getattr(&id, &fileinfo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        *usr_repnum = fileinfo.repnum_usr;

        return 0;
err_ret:
        return ret;
}

static status_t info2status(brief_info_t *info)
{
        int ret, retry0 = 0;
        int usr_repnum;

        //retry 超时时间不能太长导致etcd上key失效
        int timeout = _min(STORAGE_STATUS_UPTIME, gloconf.master_timeout + gloconf.lease_timeout);
retry:
        ret = storagepool_get_repnum(info->pool, &usr_repnum);
        if (unlikely(ret)) {
                DBUG("pool %s get repnum fail, ret:%d\n", info->pool, ret);
                if (ret != ENOKEY) {
                        USLEEP_RETRY(err_unavailable, ret, retry, retry0, timeout, (500 * 1000))
                } else {
                        goto err_unavailable;
                }
        }

        YASSERT(info->writeable_rack <= info->writeable_node);

        DBUG("pool %s rack %d node %d usr_repnum %d\n",
              info->pool, info->writeable_rack,
              info->writeable_node, usr_repnum);

        if (info->writeable_rack > 1) {
                if (usr_repnum > info->writeable_rack) {
                        goto err_degraded;
                }
        } else if (info->writeable_rack == 1) {
                if (usr_repnum > info->writeable_node) {
                        goto err_degraded;
                }
        } else {
                goto err_readonly;
        }

        return STATUS_AVAILABLE;
err_unavailable:
        return STATUS_UNAVAILABLE;
err_degraded:
        return STATUS_DEGRADED;
err_readonly:
        return STATUS_READONLY;
}

static void *__storage_status_recycle__(void *arg)
{
        int i, ret, count;
        brief_info_t *infos = NULL, *pos;
        status_t status;

        (void) arg;

        inited = TRUE;
        while (1) {
                ret = sem_wait(&__sem__);
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                ret = poolmap_brief_info(&count, &infos);
                if (unlikely(ret)) {
                        DWARN("list pool info fail %d\n", ret);
                        continue;
                }

                for (i=0; i<count; i++) {
                        pos = &infos[i];
                        status = info2status(pos);

                        ret = storage_status_set(pos->pool, status);
                        if (unlikely(ret)) {
                                DWARN("pool %s status %d ret %d\n", pos->pool, status, ret);
                        }
                }

                if (infos)
                        yfree((void **)&infos);
        }
}

static int storage_status_set(char *pool, status_t status)
{
        int ret;

        /*
         * key的超时时间, 应该大于master的切换时间和key的更新周期
         */
        int ttl = _max((2 * STORAGE_STATUS_UPTIME), (4 * (gloconf.master_timeout + gloconf.lease_timeout)));

        ret = etcd_set_with_ttl(STORAGE_STATUS_PREFIX, pool, __status__[status], ttl);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}
